-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactoring for adding other sources #4
Conversation
config.go
Outdated
@@ -73,6 +76,8 @@ type YtsaurusConfig struct { | |||
DebugUsernames []string `yaml:"debug_usernames"` | |||
// DebugGroupnames is a list of YTsaurus groupnames for which app will print more debug info in logs. | |||
DebugGroupnames []string `yaml:"debug_groupnames"` | |||
|
|||
SourceAttributeName *string `yaml:"source_attribute_name"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As with other fields it is ok to have one empty value: empty string. You making own life harder making it pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make it simple string as we discussed + add != ""
check in app start (ytsaurus object creation to be specific).
ytsaurus_helpers.go
Outdated
@@ -40,31 +45,37 @@ func doGetAllYtsaurusUsers(ctx context.Context, client yt.Client) ([]YtsaurusUse | |||
var users []YtsaurusUser | |||
for _, ytUser := range response { | |||
var bannedSince time.Time | |||
if ytUser.BannedSince != "" { | |||
bannedSince, err = time.Parse(appTimeFormat, ytUser.BannedSince) | |||
if bannedSinceRaw, ok := ytUser.Attrs[bannedSinceAttributeName]; ok && bannedSinceRaw != "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't Attrs be nil? I've had such panics when I've run tests here https://github.com/nebius/ytsaurus-active-directory-integration/pull/3/files before I've add a check for nil.
source_models.go
Outdated
@@ -0,0 +1,119 @@ | |||
package main |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code in this file should be moved to the other files (packages in the future) to be flexible and aligned with golang philosophy.
In short, golang interfaces are not intended to be used like interfaces in OOP languages, they are more like duck-typing in dynamic languages. There couple rules of thumb here:
- interfaces are declared in the place of usage (client code), often such interfaces are private for client package;
- functions should accept interfaces and return structs (it is very good rule for New functions espessially).
This is an article i've googled which can give the idea: https://alok87.in/blog/interfaces-go
Therefore:
- All interfaces should be defined in the place of usage:
Source
should move in app.goSourceUser
,SourceGroup
in diff.goSourceGroupWithMembers
also in diff.go
- AzureUser, AzureGroup and everything Azure should be in azure*.go
- NewSource{User,Group} functions shouldn't exists. It should be NewAzureUser (and NewLdapUser in the future) in azure*.go which should return AzureUser struct (not SourceUser interface).
app_test.go
Outdated
@@ -3,6 +3,7 @@ package main | |||
import ( | |||
"context" | |||
"fmt" | |||
"go.ytsaurus.tech/library/go/ptr" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File is not
goimports
-ed with -local go.ytsaurus.tech (goimports)
actually I think we should remove that rule from linter config, because this library is external to this project.
ytsaurus_helpers.go
Outdated
if sourceTypeRaw, ok := ytUser.Attrs[sourceTypeAttributeName]; ok { | ||
sourceType = SourceType(sourceTypeRaw.(string)) | ||
} | ||
sourceUser, err = NewSourceUser(sourceType, sourceRaw.(map[string]any)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I've wrote this code initially I didn't succeed to make the attribute configurable and ytsaurus code knows too much about azure fields, but it is not its area of responsibility (only knowing how to communicate with ytsaurus is). Now since we can configure attribute we can fix that flaw.
I suggest we store map[string]any
in YtsaurusUser.SourceData
and let app code delegate parsing it to the source (azure/ldap) if needed (I'm actually not sure if we need to parse map to AzureUser/LdapUser at all, because as far as i remember we only serialize one way AzureUser > YtsaurusUser).
I.e. app.buildYtsaurusUser
should either call Source
method or maybe simpler AzureUser/LdapUser/SourceUser should have a method like AsMap() map[string]any
and its result being written in YtsaurusUser.SourceData
field.
That way we ensure our components are loosely coupled.
ytsaurus_helpers.go
Outdated
const ( | ||
bannedSinceAttributeName = "banned_since" | ||
bannedAttributeName = "banned" | ||
sourceTypeAttributeName = "source_type" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to store that in ytsaurus, we have it in config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we don't. In config we have only SourceAttributeName
(for "azure" or "source" values).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my bad, but still think since source is configured in the app we can delegate handling source-fields to that source without storing it in attributes.
FirstName: aliceAzure.FirstName, | ||
LastName: aliceAzure.LastName, | ||
DisplayName: aliceAzure.DisplayName, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets simplify diff for tests by introducing some helper structs like testYtsaurusAzureUser with same fields as current YtsaurusUser and call converter function where needed in test code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't checked tests changes and diff algorithm changes yet, only main code structure.
ytsaurus_models.go
Outdated
SourceGroup SourceGroup | ||
// Members is a set of strings, representing users' ObjectID. | ||
Members StringSet | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These interfaces/structs are belong to app files, not to ytsaurus files. Particullary to diff.go since they used only there.
diff.go
Outdated
if ytUser.IsManuallyManaged(a.source.GetSourceType()) { | ||
return nil, errors.New("user is manually managed and can't be converted to source user") | ||
} | ||
sourceType := SourceType(*ytUser.SourceType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said earlier I don't see the need to duplicate source type (which is already configured in config file) in each user in ytsaurus.
I suggest we simple add NewUser
method in Source interface, implement it in azure source (calling NewAzureUser
) and here we may simply call a.source.NewUser
.
That way code would be cleaner because:
- we don't need to duplicate and store source type in all users (and we don't need to migrate our data).
- we don't need to check source type in the runtime — source method existence would be checked in the compile time
- we don't need to support list of possible source types here.
ytsaurus_helpers.go
Outdated
} | ||
} | ||
if sourceRaw, ok := ytUser.Attrs[sourceAttributeName]; ok { | ||
// For backward compatibility only. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can break backward compatibility here since I am the only app user currently.
Just lets check that sourceAttribute is not empty at the app start.
config.go
Outdated
@@ -73,6 +76,8 @@ type YtsaurusConfig struct { | |||
DebugUsernames []string `yaml:"debug_usernames"` | |||
// DebugGroupnames is a list of YTsaurus groupnames for which app will print more debug info in logs. | |||
DebugGroupnames []string `yaml:"debug_groupnames"` | |||
|
|||
SourceAttributeName *string `yaml:"source_attribute_name"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment that it will be the attribute name of user object in ytsaurus.
config.go
Outdated
@@ -73,6 +76,8 @@ type YtsaurusConfig struct { | |||
DebugUsernames []string `yaml:"debug_usernames"` | |||
// DebugGroupnames is a list of YTsaurus groupnames for which app will print more debug info in logs. | |||
DebugGroupnames []string `yaml:"debug_groupnames"` | |||
|
|||
SourceAttributeName *string `yaml:"source_attribute_name"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make it simple string as we discussed + add != ""
check in app start (ytsaurus object creation to be specific).
func (u YtsaurusUser) IsManuallyManaged() bool { | ||
return u.AzureID == "" | ||
func (u YtsaurusUser) IsManuallyManaged(sourceType SourceType) bool { | ||
return u.SourceRaw == nil || u.SourceType == nil || *u.SourceType != string(sourceType) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically managed user must have non empty/non nil SourceRaw
, let's just check emptiness of this map. And so we don't accidentally initialise this structure wrongly lets add:
NewAutoManagedYtsaurusUser(username, sourceRaw, bannedSince)
function, that would return error if function is called with emptysourceRaw
.NewManualManagedYtsaurusUser(username)
for creating manually managed users.
diff.go
Outdated
for _, user := range ytUsers { | ||
if user.IsManuallyManaged(a.source.GetSourceType()) { | ||
continue | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks paranoid since GetUsers shouldn't and don't return such users. We must (and maybe already have) have test case for such cases instead of runtime check
diff.go
Outdated
} | ||
sourceUser, err := a.buildSourceUser(&user) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to create azure user from source") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
failed to build source user
if err != nil { | ||
return nil, err | ||
} | ||
return &azureUser, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This marshall/unmarshall trick is cool, but it may give misleading feeling that AzureUser struct somehow related to yson encoding/decoding. But I think json and any other encoder can be here.
I suppose this small lib https://pkg.go.dev/github.com/mitchellh/mapstructure#example-Decode can help to replace yson marshal/unmarshal boilerplate here and in GetRaw() and same code in Groups and in future ldap source code.
But I not insist on changing this code.
azure_real_integration_test.go
Outdated
@@ -14,7 +14,7 @@ import ( | |||
//go:embed config.local.yaml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax in this file and in app_integration_test.go is broken after signature changes.
If you use idea/goland you can add integration
in custom tags field in go > build tags setting to see such issues more easily.
azure_real_integration_test.go
Outdated
@@ -14,7 +14,7 @@ import ( | |||
//go:embed config.local.yaml | |||
var _localConfig embed.FS | |||
|
|||
// TestPrintAzureUsersIntegration tests nothing, but can be used to debug Azure users retrieved from ms graph api. | |||
// TestPrintAzureUsersIntegration tests nothing, but can be used to debug Source users retrieved from ms graph api. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert back to azure i suppose
source_models.go
Outdated
|
||
const ( | ||
AzureSourceType SourceType = "azure" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose after the changes I suggest earlier we won't need this constant.
util.go
Outdated
appTimeFormat = "2006-01-02T15:04:05Z0700" | ||
defaultAzureTimeout = 3 * time.Second | ||
defaultAzureSecretEnvVar = "AZURE_CLIENT_SECRET" | ||
defaultAppRemoveLimit = 10 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THis should be in azure_real.go
ytsaurus.go
Outdated
if err != nil { | ||
return false, err | ||
} | ||
return attrAzureExists || attrSourceExists, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets use sourceAttributeName
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like only minor things left, but let's fix diffUsers
a bit (i haven't check diffGroups yet, if it has same issues then let's improve both).
config.go
Outdated
// UsersFilter is MS Graph $filter value used for user fetching requests. | ||
// See https://learn.microsoft.com/en-us/graph/api/user-list?#optional-query-parameters | ||
UsersFilter string `yaml:"users_filter"` | ||
// GroupsFilter is MS Graph $filter value used for group fetching requests. | ||
// See https://learn.microsoft.com/en-us/graph/api/group-list | ||
GroupsFilter string `yaml:"groups_filter"` | ||
GroupsFilter string `yaml:"groups_filter"` | ||
Timeout time.Duration `yaml:"timeout"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but don't understand why timeout field is moved between filters.
ytsaurus.go
Outdated
@@ -60,6 +63,9 @@ func NewYtsaurus(cfg *YtsaurusConfig, logger appLoggerType, clock clock.PassiveC | |||
if cfg.Timeout == 0 { | |||
cfg.Timeout = defaultYtsaurusTimeout | |||
} | |||
if cfg.SourceAttributeName == "" { | |||
cfg.SourceAttributeName = "source" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better make a const.
source_models.go
Outdated
@@ -0,0 +1 @@ | |||
package main |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems it is an empty file now and can be deleted :)
create: create, | ||
update: update, | ||
remove: remove, | ||
} | ||
result: resultUsersMap, | ||
}, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since tests are okey, I assume diffUsers
works correctly, but it seem overcomplicated a bit.
I see the implementation that way:
0. create maps sourceUsers [id]sourceUser
and ytUsers [id]ytsaurusUser
from slices
- iterate through
sourceUsers
map, checkytUsers
map and append tocreate
list - iterate through
ytUsers
map, checksourceUsers
map:- not found in
sourceUsers
go todelete
list - changed go to
update
list.
- not found in
Here we have
1)existedUsersMap
which has two fields and one of them (sourceUser
) is not used. So i suggest we rename it to ytUsers [id]ytsaurusUser
.
2) ytUsersFromSourceMap
which has the same keys as sourceUsers
, so we can simple drop building ytUsersFromSourceMap
and use sourceUsers
for checking and call buildYtsaurusUser
only for once we plan to create.
Also resultUsersMap is not being updating in case of user delete, though it should i guess (don't we have a test case for that?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just couple of comments folllowing up yesterday review.
Let's fix those small issues I mention and merge.
diff.go
Outdated
if err != nil { | ||
return nil, errors.Wrap(err, "failed to create azure group from source") | ||
} | ||
existedGroupsWithMembersMap[sourceGroup.GetID()] = existedGroup{group, sourceGroup} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so existedGroup.sourceGroup is not used therefore code can be little simplified here as in diffUsers i suppose
@@ -36,7 +36,7 @@ func run(configFilePath string) error { | |||
return errors.Wrapf(err, "failed to load config %s", configFilePath) | |||
} | |||
|
|||
logger, err := configureLogger(cfg.Logging) | |||
logger, err := configureLogger(&cfg.Logging) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this change must be supported in azure_real_integration_test.go also
diff.go
Outdated
return nil, errors.Wrap(err, "failed to create Ytsaurus user from source user") | ||
} | ||
ytUsersFromSourceMap[user.GetID()] = ytUser | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ytUsersFromSourceMap can be safely removed now, I don't see usages anymore
No description provided.